﻿using System;

namespace FortuneUniverse.Fortune.Algorithms
{
  public struct Quaternion : IEquatable<Quaternion>
  {
    public float x;
    public float y;
    public float z;
    public float w;
    private static readonly Quaternion identityQuaternion = new Quaternion(0.0f, 0.0f, 0.0f, 1f);
    public const float kEpsilon = 1E-06f;

    public Quaternion(float x, float y, float z, float w)
    {
      this.x = x;
      this.y = y;
      this.z = z;
      this.w = w;
    }

    public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection)
    {
      fromDirection = Vector3.Normalize(fromDirection);
      toDirection = Vector3.Normalize(toDirection);
      Vector3 axis = Vector3.Normalize(Vector3.Cross(fromDirection, toDirection));
      return Quaternion.AngleAxis(Mathf.Acos(Vector3.Dot(fromDirection, toDirection)), axis);
    }

    public static Quaternion Inverse(Quaternion rotation) => System.Numerics.Quaternion.Inverse(rotation);

    public static Quaternion Slerp(Quaternion a, Quaternion b, float t) => System.Numerics.Quaternion.Slerp(a, b, t);

    public static Quaternion AngleAxis(float angle, Vector3 axis) => System.Numerics.Quaternion.CreateFromAxisAngle(new System.Numerics.Vector3(axis.x, axis.y, axis.z), angle);

    public float this[int index]
    {
      get
      {
        switch (index)
        {
          case 0:
            return this.x;
          case 1:
            return this.y;
          case 2:
            return this.z;
          case 3:
            return this.w;
          default:
            throw new IndexOutOfRangeException("Invalid Quaternion index!");
        }
      }
      set
      {
        switch (index)
        {
          case 0:
            this.x = value;
            break;
          case 1:
            this.y = value;
            break;
          case 2:
            this.z = value;
            break;
          case 3:
            this.w = value;
            break;
          default:
            throw new IndexOutOfRangeException("Invalid Quaternion index!");
        }
      }
    }

    public void Set(float newX, float newY, float newZ, float newW)
    {
      this.x = newX;
      this.y = newY;
      this.z = newZ;
      this.w = newW;
    }

    public static Quaternion identity => Quaternion.identityQuaternion;

    public static Quaternion operator *(Quaternion lhs, Quaternion rhs) => new Quaternion((float) ((double) lhs.w * (double) rhs.x + (double) lhs.x * (double) rhs.w + (double) lhs.y * (double) rhs.z - (double) lhs.z * (double) rhs.y), (float) ((double) lhs.w * (double) rhs.y + (double) lhs.y * (double) rhs.w + (double) lhs.z * (double) rhs.x - (double) lhs.x * (double) rhs.z), (float) ((double) lhs.w * (double) rhs.z + (double) lhs.z * (double) rhs.w + (double) lhs.x * (double) rhs.y - (double) lhs.y * (double) rhs.x), (float) ((double) lhs.w * (double) rhs.w - (double) lhs.x * (double) rhs.x - (double) lhs.y * (double) rhs.y - (double) lhs.z * (double) rhs.z));

    public static Vector3 operator *(Quaternion rotation, Vector3 point)
    {
      float num1 = rotation.x * 2f;
      float num2 = rotation.y * 2f;
      float num3 = rotation.z * 2f;
      float num4 = rotation.x * num1;
      float num5 = rotation.y * num2;
      float num6 = rotation.z * num3;
      float num7 = rotation.x * num2;
      float num8 = rotation.x * num3;
      float num9 = rotation.y * num3;
      float num10 = rotation.w * num1;
      float num11 = rotation.w * num2;
      float num12 = rotation.w * num3;
      Vector3 vector3;
      vector3.x = (float) ((1.0 - ((double) num5 + (double) num6)) * (double) point.x + ((double) num7 - (double) num12) * (double) point.y + ((double) num8 + (double) num11) * (double) point.z);
      vector3.y = (float) (((double) num7 + (double) num12) * (double) point.x + (1.0 - ((double) num4 + (double) num6)) * (double) point.y + ((double) num9 - (double) num10) * (double) point.z);
      vector3.z = (float) (((double) num8 - (double) num11) * (double) point.x + ((double) num9 + (double) num10) * (double) point.y + (1.0 - ((double) num4 + (double) num5)) * (double) point.z);
      return vector3;
    }

    private static bool IsEqualUsingDot(float dot) => (double) dot > 0.999998986721039;

    public static bool operator ==(Quaternion lhs, Quaternion rhs) => Quaternion.IsEqualUsingDot(Quaternion.Dot(lhs, rhs));

    public static bool operator !=(Quaternion lhs, Quaternion rhs) => !(lhs == rhs);

    public static float Dot(Quaternion a, Quaternion b) => (float) ((double) a.x * (double) b.x + (double) a.y * (double) b.y + (double) a.z * (double) b.z + (double) a.w * (double) b.w);

    public static float Angle(Quaternion a, Quaternion b)
    {
      float num = Quaternion.Dot(a, b);
      return Quaternion.IsEqualUsingDot(num) ? 0.0f : (float) ((double) Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2.0 * 57.2957801818848);
    }

    private static Vector3 Internal_MakePositive(Vector3 euler)
    {
      float num1 = (float) (-9f / (500f * Math.PI));
      float num2 = 360f + num1;
      if ((double) euler.x < (double) num1)
        euler.x += 360f;
      else if ((double) euler.x > (double) num2)
        euler.x -= 360f;
      if ((double) euler.y < (double) num1)
        euler.y += 360f;
      else if ((double) euler.y > (double) num2)
        euler.y -= 360f;
      if ((double) euler.z < (double) num1)
        euler.z += 360f;
      else if ((double) euler.z > (double) num2)
        euler.z -= 360f;
      return euler;
    }

    public static Quaternion Normalize(Quaternion q)
    {
      float num = Mathf.Sqrt(Quaternion.Dot(q, q));
      return (double) num >= 1E-06 ? new Quaternion(q.x / num, q.y / num, q.z / num, q.w / num) : Quaternion.identity;
    }

    public void Normalize() => this = Quaternion.Normalize(this);

    public Quaternion normalized => Quaternion.Normalize(this);

    public override int GetHashCode() => this.x.GetHashCode() ^ this.y.GetHashCode() << 2 ^ this.z.GetHashCode() >> 2 ^ this.w.GetHashCode() >> 1;

    public override bool Equals(object other) => other is Quaternion other1 && this.Equals(other1);

    public bool Equals(Quaternion other) => this.x.Equals(other.x) && this.y.Equals(other.y) && this.z.Equals(other.z) && this.w.Equals(other.w);

    public void SetAxisAngle(Vector3 axis, float angle) => this = Quaternion.AxisAngle(axis, angle);

    public static Quaternion AxisAngle(Vector3 axis, float angle) => Quaternion.AngleAxis(57.29578f * angle, axis);

    public static implicit operator System.Numerics.Quaternion(Quaternion q) => new System.Numerics.Quaternion(q.x, q.y, q.z, q.w);

    public static implicit operator Quaternion(System.Numerics.Quaternion q) => new Quaternion(q.X, q.Y, q.Z, q.W);
  }
}
